#include "time.h"
#include "io.h"
#include "kernel.h"
#include "idt.h"

#define CMOS_ADDR 0x70
#define CMOS_DATA 0x71
#define CMOS_SECOND 0x00
#define CMOS_MINUTE 0x02
#define CMOS_HOUR 0x04
#define CMOS_WEEKDAY 0x06
#define CMOS_DAY 0x07
#define CMOS_MONTH 0x08
#define CMOS_YEAR 0x09
#define CMOS_CENTURY 0x32
#define CMOS_NMI 0x80

#define MINUTE 60
#define HOUR (60 * MINUTE)
#define DAY (24 * HOUR)
#define YEAR (365 * DAY)

static int month[13] = {
    0, 
    0,
    (31),
    (31 + 29),
    (31 + 29 + 31),
    (31 + 29 + 31 + 30),
    (31 + 29 + 31 + 30 + 31),
    (31 + 29 + 31 + 30 + 31 + 30),
    (31 + 29 + 31 + 30 + 31 + 30 + 31),
    (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31),
    (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30),
    (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31),
    (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30),
};

typedef u32 time_t;

extern long startup_time;

int century;

u8 bcd_to_bin(u8 value)
{
    return (value & 0xf) + (value >> 4) * 10;
}

u8 bin_to_bcd(u8 value)
{
    return (value % 10) + ((value / 10) << 4);
}

time_t mktime(tm * time)
{
    time_t res;
    int year;
    if (time->tm_year >= 70)
    {
        year = time->tm_year - 70;
    }
    else 
    {
        year = time->tm_year - 70 + 100;
    }

    res = YEAR * year;
    
    res += DAY * ((year + 1) / 4);

    res += month[time->tm_mon] * DAY;

    if (time->tm_mon > 2 && ((year + 2) %4))
    {   
        res -= DAY;
    }
    
    res += DAY * (time->tm_mday - 1);

    res += HOUR * time->tm_hour;

    res += MINUTE * time->tm_min;

    res += time->tm_sec;

    return res;
}

int get_yday(tm * time)
{
    int res = month[time->tm_mon];
    res += time->tm_mday;

    int year;
    if (time->tm_year >= 70)
    {
        year = time->tm_year - 70;
    } 
    else 
    {
        year = time->tm_year - 70 + 100;
    }
    
    if ((year + 2) % 4 && time->tm_mon > 2)
    {
        res -= 1;
    }
    return res;
}

u8 cmos_read(u8 addr)
{
    out_byte(CMOS_ADDR, CMOS_NMI | addr);
    return in_byte(CMOS_DATA);
}

void time_read_bcd(tm * time)
{
    do {
        time->tm_sec = cmos_read(CMOS_SECOND);
        time->tm_min = cmos_read(CMOS_MINUTE);
        time->tm_hour = cmos_read(CMOS_HOUR);
        time->tm_wday = cmos_read(CMOS_WEEKDAY);
        time->tm_mday = cmos_read(CMOS_DAY);
        time->tm_mon = cmos_read(CMOS_MONTH);
        time->tm_year = cmos_read(CMOS_YEAR);
        century = cmos_read(CMOS_CENTURY);
    } while (time->tm_sec != cmos_read(CMOS_SECOND));
}

void time_read(tm *time)
{
    time_read_bcd(time);
    time->tm_sec = bcd_to_bin(time->tm_sec);
    time->tm_min = bcd_to_bin(time->tm_min);
    time->tm_hour = bcd_to_bin(time->tm_hour);
    time->tm_wday = bcd_to_bin(time->tm_wday);
    time->tm_mday = bcd_to_bin(time->tm_mday);
    time->tm_mon = bcd_to_bin(time->tm_mon);
    time->tm_year = bcd_to_bin(time->tm_year);
    time->tm_yday = get_yday(time);
    time->tm_isdst = -1;

    century = bcd_to_bin(century);
}

static void print_time(tm* time)
{
    printk("%d%02d-%02d-%02d ", century, time->tm_year, time->tm_mon, time->tm_mday);
    printk("%02d:%02d:%02d(%d)\n", time->tm_hour, time->tm_min, time->tm_sec, startup_time);
}

void time_init(void)
{
    tm time;
    time_read(&time);
    startup_time = mktime(&time);

    // print_time(&time);
}

// 此函数跟老师的代码运行结果略有不同，原因不知。
// 老师的代码周期性中断打印很频繁，而我的打印频率较慢，但不影响功能。
void rtc_interrupt_handler(void)
{
    // 先读寄存器c，判断发生的是什么中断
    // 如果发生周期性中断，位6为1,如果发生了更新结束中断，位4为1
    uchar creg = 0;
    out_byte(0x70, 0x8c);
    creg = in_byte(0x71);

    // 发生更新周期结束中断
    if (creg & 0x10)
    {
        tm time = {0};
        time_read(&time);
        print_time(&time);
    }
    
    // 发生周期性中断
    if (creg & 0x40)
    {
        printk("period interrupt!\n");
    }
    send_eoi(0x28);
}